#pragma once

#include <functional>
#include <queue>
#include <thread>
#include <mutex>
#include <atomic>

class async_proc {
public:
	async_proc() {
		tid = std::thread([this]() {
			while (live) {
				std::unique_lock<std::mutex> lk_(cv_mtx);
				while (live && task_cnt == 0)
					task_cv.wait(lk_);
				if (!live)
					break;
				task_mtx.lock();
				while (task_que.size()) {
					auto tsk = task_que.front();
					task_que.pop();
					task_mtx.unlock();
					--task_cnt;
					tsk();
					task_mtx.lock();
				}
				task_mtx.unlock();
			}
			});
	}
	~async_proc() {
		live = false;
		task_cv.notify_one();
		tid.join();
	}

	template <typename F, typename... Args>
	void post_task(F&& f, Args &&... args)
	{
		task tsk = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
		task_mtx.lock();
		task_que.push(tsk);
		task_mtx.unlock();
		++task_cnt;
		task_cv.notify_one();
	}

	async_proc(const async_proc&) = delete;
	async_proc& operator=(const async_proc&) = delete;

private:
	using task = std::function<void()>;
	std::queue<task> task_que;
	std::mutex task_mtx;
	std::atomic<bool> live = true;
	std::thread tid;
	std::condition_variable task_cv;
	std::mutex cv_mtx;
	std::atomic<int> task_cnt = 0;
};
